package com.java.hohenheim.springplatform.web.jwt;

import com.java.hohenheim.springplatform.define.ResultCodes;
import io.jsonwebtoken.*;
import lombok.extern.slf4j.Slf4j;
import net.logstash.logback.encoder.org.apache.commons.lang3.StringUtils;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;
import java.util.Date;
import java.util.Map;

/**
 * @author Hohenheim
 */
@Slf4j
public class JwtUtils {
    /**
     * 签发token，指定时间内过期
     *
     * @param claimMap
     * @return 加密的token
     */
    public String createToken(Map<String, Object> claimMap, TokenConfig config) {
        SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;
        byte[] keyBytes = config.getSignKey().getBytes();
        byte[] encodedKey = Base64.getEncoder().encode(keyBytes);
        SecretKey secKey = new SecretKeySpec(encodedKey, signatureAlgorithm.getJcaName());

        //设置Token有效时间
        Date issueDate = new Date();

        long validityTime = config.getExpireSeconds() * 1000L;
        long expirationTime = issueDate.getTime() + validityTime;
        Date expirationDate = new Date(expirationTime);

        JwtBuilder builder = Jwts.builder()
                .setHeaderParam("typ", "JWT")
                .setHeaderParam("alg", signatureAlgorithm.getValue())
                .setIssuedAt(issueDate)
                .setExpiration(expirationDate)
                .setSubject(config.getSubject())
                .setIssuer(config.getIssuer());

        if(null != claimMap && !claimMap.isEmpty()) {
            claimMap.forEach(builder::claim);
        }

        builder.signWith(signatureAlgorithm, secKey);
        return builder.compact();
    }

    /**
     * 校验token是否正确
     *
     * @param token 凭证
     * @return 检测结果，如果检测成功，还包含Token信息对象
     */
    public TokenCheckResult<JwtToken> verify(String token, String signKey) {
        TokenCheckResult<JwtToken> result = new TokenCheckResult<>();
        if(StringUtils.isBlank(token)) {
            result.setResult(ResultCodes.TOKEN_SIGN_EXCEPTION);
            return result;
        }

        //解密Token
        try {
            Claims claims = getClaimsFromToken(token, signKey,true);

            JwtToken jwtToken = new JwtToken();
            jwtToken.setSubject(claims.getSubject());
            jwtToken.setExpirationDate(claims.getExpiration());
            jwtToken.setIssuer(claims.getIssuer());
            jwtToken.setIssuedAt(claims.getIssuedAt());

            //设置检测结果
            result.setJwtToken(jwtToken);
            result.setCheckResult(true);
            result.setResult(ResultCodes.RETURN_SUCCESS);
        }
        catch (ExpiredJwtException e) {
            log.debug("Token " + token + " 已失效");
            result.setResult(ResultCodes.TOKEN_EXPIRED);
        }
        catch (SignatureException e) {
            log.error("[Norla-authentication] 验签失败，非法Token：" + token, e);
            result.setResult(ResultCodes.TOKEN_SIGN_EXCEPTION);
        }
        catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException e) {
            log.error("[Norla-authentication] 解析失败，非法Token：" + token, e);
            result.setResult(ResultCodes.TOKEN_SIGN_EXCEPTION);
        }

        return result;
    }

    protected JwtToken getTokenData(String tokenStr, String signKey, boolean throwable) {
        Claims claims = getClaimsFromToken(tokenStr, signKey, throwable);
        JwtToken jwtToken = getTokenData(claims);

        return jwtToken;
    }

    protected JwtToken getTokenData(Claims claims) {
        JwtToken jwtToken = null;

        if(null != claims) {
            jwtToken = new JwtToken();
            jwtToken.setSubject(claims.getSubject());
            jwtToken.setIssuer(claims.getIssuer());
            jwtToken.setIssuedAt(claims.getIssuedAt());
            jwtToken.setExpirationDate(claims.getExpiration());
        }

        return jwtToken;
    }

    protected Claims getClaimsFromToken(String token, String signKey, boolean throwable) {
        byte[] keyBytes = signKey.getBytes();
        byte[] encodedKey = Base64.getEncoder().encode(keyBytes);

        Claims claims = null;
        try {
            claims = Jwts.parser()
                    .setSigningKey(encodedKey)
                    .parseClaimsJws(token)
                    .getBody();
        }
        catch (Exception e) {
            if(throwable) {
                throw e;
            }
            else {
                log.error("Token解析错误" + e.getMessage(), e);
            }
        }

        return claims;
    }
}